<?php
namespace PhpBoot\ORM;

use PhpBoot\DB\DB;
use PhpBoot\DB\rules\select\JoinRule;
use PhpBoot\DB\rules\select\WhereRule;

class ModelWithClass
{
    /**
     * @var bool
     */
    private $useSub;

    /**
     * Model constructor.
     * @param DB $db
     * @param string $entityName
     */
    public function __construct(DB $db, $entityName)
    {
        $this->db = $db;
        $builder = $db->getApp()->get(ModelContainerBuilder::class);
        $this->entity = $builder->build($entityName);
        $this->useSub = $this->entity->getSubKey() && $this->entity->getSubTable();
    }

    /**
     * @return void
     */
    public function unSub()
    {
        $this->useSub = false;
    }

    /**
     * @param string $id
     * @return mixed|null
     */
    public function find($id)
    {
        $query = $this->getQuery();
        $row = $query->where("`{$this->entity->getPK()}` = ?", $id)
            ->getFirst();
        if($row){
            return $this->entity->make($row, false);
        }else{
            return null;
        }
    }

    /**
     * @return int rows deleted
     */
    public function delete($id)
    {
        return $this->deleteWhere([$this->entity->getPK()=>$id])->exec()->rows;
    }

    /**
     * where 语法见 @see WhereRule
     * @param array|string $expr
     * @param mixed|null $_
     * @return \PhpBoot\DB\rules\basic\WhereRule
     */
    public function deleteWhere($conditions, $_=null)
    {
        $query = $this->getDeleteQuery();
        return call_user_func_array([$query, 'where'], func_get_args());
    }

    /**
     * @return false|int
     */
    public function count()
    {
        return $this->db->select($this->getColumns())
            ->from($this->entity->getTable())
            ->count();
    }

    /**
     * where 语法见 @see WhereRule
     * @param array|string|callable|null $conditions
     * @param string $_
     * @return WhereRule
     */
    public function findWhere($conditions=null, $_=null)
    {
        $query = $this->getQuery();
        $query->context->resultHandler = function ($result){
            foreach ($result as &$i){
                $i = $this->entity->make($i, false);
            }
            return $result;
        };
        return call_user_func_array([$query, 'where'], func_get_args());
    }

    /**
     * @param int|string $id
     * @param array $values
     * @return int updated row count
     */
    public function update($id, $values)
    {
        list($data, $subData) = $this->getUpdateData($values);
        return $this->db->transaction(function (DB $db) use ($id, $data, $subData) {
            $rows = !empty($data)
                ? $db->update($this->entity->getTable())
                    ->set($data)
                    ->where([$this->entity->getPK() => $id])
                    ->exec()->rows
                : 0;
            $subRows = 0;
            if ($this->useSub && !empty($subData)) {
                $subRows = $db->update($this->entity->getSubTable())
                    ->set($subData)
                    ->where([$this->entity->getSubKey()=>$id])
                    ->exec()->rows;
            }
            return $rows ? : $subRows;
        });
    }

    /**
     * @param array $values
     * @param array|string|callable $conditions  where 语法见 @see WhereRule
     * @param string $_
     * @return \PhpBoot\DB\rules\basic\WhereRule
     */
    public function updateWhere($values, $conditions, $_=null)
    {
        $query =  $this->getUpdateQuery()->set($values);
        return call_user_func_array([$query, 'where'], array_slice(func_get_args(),1));
    }
    private function getUpdateData($values)
    {
        $data = $subData = [];
        $subColumns = $this->getColumns(true);
        foreach ($values as $k => $value){
            if(in_array($k, $subColumns)){
                $subData[$k] = $value;
            } else {
                // 默认置入主表，兼容PO中漏加字段
                $data[$k] = $value;
            }
        }
        return [$data, $subData];
    }

    /**
     * @param array $values
     * @return int
     */
    public function insertBatch(array $values)
    {
        return $this->db->insertInto($this->entity->getTable())
            ->batchValues($values)
            ->exec()->rows;
    }

    /**
     * set entity table name
     * @param string $tableName
     * @return $this
     */
    public function withTable($tableName)
    {
        $this->entity->setTable($tableName);
        return $this;
    }

    protected function getColumns($sub = false)
    {
        $columns = [];
        foreach ($this->entity->getProperties() as $p){
            if ($p->isSub == $sub) {
                $columns[] = $p->name;
            }
        }
        return $columns;
    }

    private function getAllColumns()
    {
        $columns = [];
        foreach ($this->entity->getProperties() as $p){
            $columns[] = $p->name;
        }
        return $columns;

    }

    /**
     * @return JoinRule
     */
    private function getQuery()
    {
        $columns = $this->useSub ? $this->getAllColumns() : $this->getColumns();
        return $this->getJoinQuery($this->db->select($columns)->from($this->entity->getTable()));
    }

    /**
     * @return \PhpBoot\DB\rules\update\JoinRule
     */
    private function getUpdateQuery()
    {
        return $this->getJoinQuery($this->db->update($this->entity->getTable()));
    }

    /**
     * @return \PhpBoot\DB\rules\delete\JoinRule
     */
    private function getDeleteQuery()
    {
        $query = $this->db->deleteFrom($this->entity->getTable(), $this->entity->getSubTable());
        if ($this->useSub) {
            $query = $query
                ->leftJoin(DB::raw($this->entity->getSubTable() . ' AS s'))
                ->on('s.' . $this->entity->getSubKey() . '= m.' . $this->entity->getPK());
        }
        return $query;
    }

    /**
     * @param JoinRule|\PhpBoot\DB\rules\update\JoinRule|\PhpBoot\DB\rules\delete\JoinRule $query
     * @return JoinRule|\PhpBoot\DB\rules\update\JoinRule|\PhpBoot\DB\rules\delete\JoinRule
     */
    private function getJoinQuery($query)
    {
        if ($this->useSub) {
            $query = $query
                ->leftJoin(DB::raw($this->entity->getSubTable()))
                ->on($this->entity->getSubKey() . '=' . $this->entity->getPK());
        }
        return $query;
    }
    /**
     * @var ModelContainer
     */
    protected $entity;
    /**
     * @var DB
     */
    protected $db;
}